#ifndef D_MATRIX_H
#define D_MATRIX_H

#include <math.h>
#include <helpers\math\vector3.h>
#include <helpers\math\math_helper.h>

namespace DelGE
{
	namespace Helpers
	{
		namespace Math
		{
			struct Matrix
			{
				float fData[16];

				Matrix();
				Matrix(const Matrix &rOther);
				~Matrix();

				static Matrix Identity();
				static Matrix Create2DTranslation(float fX, float fY);
				static Matrix Create2DScale(float fX, float fY);
				static Matrix Create2DRotation(float fAngle);
				static Matrix Create3DTranslation(float fX, float fY, float fZ);
				static Matrix Create3DScale(float fX, float fY, float fZ);
				static Matrix CreateRotationPitch(float fAngle);
				static Matrix CreateRotationYaw(float fAngle);
				static Matrix CreateRotationRoll(float fAngle);
				static Matrix CreateOrthographicsProjection(float fLeft, float fRight, float fBottom, float fTop, float fNear, float fFar);
				static Matrix CreatePerspectiveProjection(float fFieldOfView, float fAspect, float fNear, float fFar);
				static Matrix CreateClippingMatrix(float fWidth, float fHeight, float fFieldOfView);

				Matrix Transpose();
				Matrix Inverse();

				Matrix& operator =(const Matrix &rOther);
				float& operator [](unsigned int uiIndex);
				float operator [](unsigned int uiIndex) const;
				float& operator ()(unsigned int uiRow, unsigned int uiColumn);
				float operator ()(unsigned int uiRow, unsigned int uiColumn) const;
			};

			Matrix operator *(const Matrix& rMatrix0, const Matrix& rMatrix1);
			Vector3 operator *(const Vector3& rVector, const Matrix& rMatrix);
			Vector3 operator *(const Matrix& rMatrix, const Vector3& rVector);

			inline Matrix Matrix::Create2DTranslation(float fX, float fY)
			{
				return Matrix::Create3DTranslation(fX, fY, 0);
			}

			inline Matrix Matrix::Create2DScale(float fX, float fY)
			{
				return Matrix::Create3DScale(fX, fY, 0);
			}

			inline Matrix Matrix::Create2DRotation(float fAngle)
			{
				Matrix result;
				float cosine, sine, radians;

				result = Matrix::Identity();

				radians = MathHelper::DegreesToRadians(fAngle);
				cosine = cos(radians);
				sine = sin(radians);

				result(0, 0) = cosine;	result(1, 0) = -sine;
				result(0, 1) = sine;	result(1, 1) = cosine;

				return result;
			}

			inline Matrix Matrix::Create3DTranslation(float fX, float fY, float fZ)
			{
				Matrix result;

				result(3, 0) = fX;
				result(3, 1) = fY;
				result(3, 2) = fZ;

				return result;
			}
			
			inline Matrix Matrix::Create3DScale(float fX, float fY, float fZ)
			{
				Matrix result;

				result(0, 0) = fX;
				result(1, 1) = fY;
				result(2, 2) = fZ;
				result(3, 3) = 1;

				return result;
			}

			inline Matrix Matrix::CreateRotationPitch(float fAngle)
			{
				Matrix result;
				float cosine, sine, radians;

				result = Matrix::Identity();

				radians = MathHelper::DegreesToRadians(fAngle);
				cosine = cos(radians);
				sine = sin(radians);

				result(1, 1) = cosine;	result(2, 1) = -sine;
				result(1, 2) = sine;	result(2, 2) = cosine;

				return result;
			}
			
			inline Matrix Matrix::CreateRotationYaw(float fAngle)
			{
				Matrix result;
				float cosine, sine, radians;

				result = Matrix::Identity();

				radians = MathHelper::DegreesToRadians(fAngle);
				cosine = cos(radians);
				sine = sin(radians);

				result[0] = cosine;	result[8] = sine;
				result[2] = -sine;	result[10] = cosine;

				return result;
			}
			
			inline Matrix Matrix::CreateRotationRoll(float fAngle)
			{
				return Matrix::Create2DRotation(fAngle);
			}

			inline Matrix Matrix::CreateOrthographicsProjection(float fLeft, float fRight, float fBottom, float fTop, float fNear, float fFar)
			{
				Matrix result;

				float fRDifference = 1.0f / (fRight - fLeft);
				float fUDifference = 1.0f / (fTop - fBottom);
				float fDDifference = 1.0f / (fFar - fNear);

				result(0, 0) = 2.0f * fRDifference;		result(3, 0) = -1 * (fRight + fLeft) * fRDifference;
				result(1, 1) = 2.0f * fUDifference;		result(3, 1) = -1 * (fTop + fBottom) * fUDifference;
				result(2, 2) = -2.0f * fDDifference;	result(3, 2) = -1 * (fFar + fNear) * fDDifference;
				result(3, 3) = 1;

				return result;
			}

			inline Matrix Matrix::CreatePerspectiveProjection(float fFieldOfView, float fAspect, float fNear, float fFar)
			{
				Matrix result;

				float fXYMax, fYMin, fXMin;
				float fWidth, fHeight;
				float fDepth, fQ, fQN;
				float fW, fH;
				
				fXYMax = fNear * tan(fFieldOfView * PI_OVER_360);
				fYMin = fXMin = -fXYMax;

				fWidth = fXYMax - fXMin;
				fHeight = fXYMax - fYMin;

				fDepth = fFar - fNear;
				fQ = -(fFar + fNear) / fDepth;
				fQN = -2 * (fFar * fNear) / fDepth;

				fW = (2 * fNear / fWidth) / fAspect;
				fH = (2 * fNear / fHeight);

				result[0] = fW;	result[4] = 0;	result[8] = 0;	result[12] = 0;
				result[1] = 0;	result[5] = fH;	result[9] = 0;	result[13] = 0;
				result[2] = 0;	result[6] = 0;	result[10] = fQ;result[14] = fQN;
				result[3] = 0;	result[7] = 0;	result[11] = -1;result[15] = 0;

				return result;
			}

			inline Matrix Matrix::CreateClippingMatrix(float fWidth, float fHeight, float fFieldOfView)
			{
				Matrix result;

				float fAspect, fW;

				fAspect = fWidth / fHeight;

				fW = (fFieldOfView * PI_OVER_360) * fAspect;

				result[0] = fW;	result[1] = 0;				result[2] = 0;	result[3] = 0;
				result[4] = 0;	result[5] = fFieldOfView;	result[6] = 0;	result[7] = 0;
				result[8] = 0;	result[9] = 0;				result[10] = 1; result[11] = 1;
				result[12] = 0;	result[13] = 0;				result[14] = -1;result[15] = 0;

				return result;
			}

			inline Matrix Matrix::Transpose()
			{
				Matrix transposeMatrix;

				transposeMatrix(0, 0) = (*this)(0, 0); transposeMatrix(1, 0) = (*this)(0, 1); transposeMatrix(2, 0) = (*this)(0, 2); transposeMatrix(3, 0) = (*this)(0, 3);
				transposeMatrix(0, 1) = (*this)(1, 0); transposeMatrix(1, 1) = (*this)(1, 1); transposeMatrix(2, 1) = (*this)(1, 2); transposeMatrix(3, 1) = (*this)(1, 3);
				transposeMatrix(0, 2) = (*this)(2, 0); transposeMatrix(1, 2) = (*this)(2, 1); transposeMatrix(2, 2) = (*this)(2, 2); transposeMatrix(3, 2) = (*this)(2, 3);
				transposeMatrix(0, 3) = (*this)(3, 0); transposeMatrix(1, 3) = (*this)(3, 1); transposeMatrix(2, 3) = (*this)(3, 2); transposeMatrix(3, 3) = (*this)(3, 3);

				return transposeMatrix;
			}

			inline Matrix Matrix::Inverse()
			{
				Matrix inverseMatrix;
				Vector3 translationVector;

				// Transpose 3 x 3 part
				inverseMatrix(0, 0) = (*this)(0, 0); inverseMatrix(1, 0) = (*this)(0, 1); inverseMatrix(2, 0) = (*this)(0, 2); inverseMatrix(3, 0) = 0;
				inverseMatrix(0, 1) = (*this)(1, 0); inverseMatrix(1, 1) = (*this)(1, 1); inverseMatrix(2, 1) = (*this)(1, 2); inverseMatrix(3, 1) = 0;
				inverseMatrix(0, 2) = (*this)(2, 0); inverseMatrix(1, 2) = (*this)(2, 1); inverseMatrix(2, 2) = (*this)(2, 2); inverseMatrix(3, 2) = 0;
				inverseMatrix(0, 3) = 0;			 inverseMatrix(1, 3) = 0;			  inverseMatrix(2, 3) = 0;			   inverseMatrix(3, 3) = 1;

				// Get translation part
				translationVector[0] = (*this)(3, 0);
				translationVector[1] = (*this)(3, 1);
				translationVector[2] = (*this)(3, 2);

				// Invert the translation
				inverseMatrix(3, 0) = ( ((*this)(0, 0) * translationVector[0]) + ((*this)(0, 1) * translationVector[1]) + ((*this)(0, 2) * translationVector[2]) ) * -1;
				inverseMatrix(3, 1) = ( ((*this)(1, 0) * translationVector[0]) + ((*this)(1, 1) * translationVector[1]) + ((*this)(1, 2) * translationVector[2]) ) * -1;
				inverseMatrix(3, 2) = ( ((*this)(2, 0) * translationVector[0]) + ((*this)(2, 1) * translationVector[1]) + ((*this)(2, 2) * translationVector[2]) ) * -1;

				return inverseMatrix;
			}

			inline Matrix& Matrix::operator =(const Matrix &rOther)
			{
				for (unsigned int i = 0; i < 16; i ++)
					(*this)[i] = rOther[i];

				return *this;
			}

			inline float& Matrix::operator [](unsigned int uiIndex)
			{
				if (uiIndex > 15) uiIndex = 15;
				return this->fData[uiIndex];
			}

			inline float Matrix::operator [](unsigned int uiIndex) const
			{
				if (uiIndex > 15) uiIndex = 15;
				return this->fData[uiIndex];
			}

			inline float& Matrix::operator ()(unsigned int uiRow, unsigned int uiColumn)
			{
				return this->fData[uiColumn + 4 * uiRow];
			}

			inline float Matrix::operator ()(unsigned int uiRow, unsigned int uiColumn) const
			{
				return this->fData[uiColumn + 4 * uiRow];
			}

			inline Matrix operator *(const Matrix& rMatrix0, const Matrix& rMatrix1)
			{
				Matrix result;
				
				result[ 0] = rMatrix1[ 0] * rMatrix0[ 0] + rMatrix1[ 1] * rMatrix0[ 4] + rMatrix1[ 2] * rMatrix0[ 8] + rMatrix1[ 3] * rMatrix0[12];
				result[ 1] = rMatrix1[ 0] * rMatrix0[ 1] + rMatrix1[ 1] * rMatrix0[ 5] + rMatrix1[ 2] * rMatrix0[ 9] + rMatrix1[ 3] * rMatrix0[13];
				result[ 2] = rMatrix1[ 0] * rMatrix0[ 2] + rMatrix1[ 1] * rMatrix0[ 6] + rMatrix1[ 2] * rMatrix0[10] + rMatrix1[ 3] * rMatrix0[14];
				result[ 3] = rMatrix1[ 0] * rMatrix0[ 3] + rMatrix1[ 1] * rMatrix0[ 7] + rMatrix1[ 2] * rMatrix0[11] + rMatrix1[ 3] * rMatrix0[15];

				result[ 4] = rMatrix1[ 4] * rMatrix0[ 0] + rMatrix1[ 5] * rMatrix0[ 4] + rMatrix1[ 6] * rMatrix0[ 8] + rMatrix1[ 7] * rMatrix0[12];
				result[ 5] = rMatrix1[ 4] * rMatrix0[ 1] + rMatrix1[ 5] * rMatrix0[ 5] + rMatrix1[ 6] * rMatrix0[ 9] + rMatrix1[ 7] * rMatrix0[13];
				result[ 6] = rMatrix1[ 4] * rMatrix0[ 2] + rMatrix1[ 5] * rMatrix0[ 6] + rMatrix1[ 6] * rMatrix0[10] + rMatrix1[ 7] * rMatrix0[14];
				result[ 7] = rMatrix1[ 4] * rMatrix0[ 3] + rMatrix1[ 5] * rMatrix0[ 7] + rMatrix1[ 6] * rMatrix0[11] + rMatrix1[ 7] * rMatrix0[15];

				result[ 8] = rMatrix1[ 8] * rMatrix0[ 0] + rMatrix1[ 9] * rMatrix0[ 4] + rMatrix1[10] * rMatrix0[ 8] + rMatrix1[11] * rMatrix0[12];
				result[ 9] = rMatrix1[ 8] * rMatrix0[ 1] + rMatrix1[ 9] * rMatrix0[ 5] + rMatrix1[10] * rMatrix0[ 9] + rMatrix1[11] * rMatrix0[13];
				result[10] = rMatrix1[ 8] * rMatrix0[ 2] + rMatrix1[ 9] * rMatrix0[ 6] + rMatrix1[10] * rMatrix0[10] + rMatrix1[11] * rMatrix0[14];
				result[11] = rMatrix1[ 8] * rMatrix0[ 3] + rMatrix1[ 9] * rMatrix0[ 7] + rMatrix1[10] * rMatrix0[11] + rMatrix1[11] * rMatrix0[15];

				result[12] = rMatrix1[12] * rMatrix0[ 0] + rMatrix1[13] * rMatrix0[ 4] + rMatrix1[14] * rMatrix0[ 8] + rMatrix1[15] * rMatrix0[12];
				result[13] = rMatrix1[12] * rMatrix0[ 1] + rMatrix1[13] * rMatrix0[ 5] + rMatrix1[14] * rMatrix0[ 9] + rMatrix1[15] * rMatrix0[13];
				result[14] = rMatrix1[12] * rMatrix0[ 2] + rMatrix1[13] * rMatrix0[ 6] + rMatrix1[14] * rMatrix0[10] + rMatrix1[15] * rMatrix0[14];
				result[15] = rMatrix1[12] * rMatrix0[ 3] + rMatrix1[13] * rMatrix0[ 7] + rMatrix1[14] * rMatrix0[11] + rMatrix1[15] * rMatrix0[15];

				return result;
			}

			inline Vector3 operator *(const Vector3& rVector, const Matrix& rMatrix)
			{
				Vector3 result;

				result[0] = rVector[0] * rMatrix(0, 0) + rVector[1] * rMatrix(1, 0) + rVector[2] * rMatrix(2, 0) + rVector[3] * rMatrix(3, 0);
				result[1] = rVector[0] * rMatrix(0, 1) + rVector[1] * rMatrix(1, 1) + rVector[2] * rMatrix(2, 1) + rVector[3] * rMatrix(3, 1);
				result[2] = rVector[0] * rMatrix(0, 2) + rVector[1] * rMatrix(1, 2) + rVector[2] * rMatrix(2, 2) + rVector[3] * rMatrix(3, 2);
				result[3] = rVector[0] * rMatrix(0, 3) + rVector[1] * rMatrix(1, 3) + rVector[2] * rMatrix(2, 3) + rVector[3] * rMatrix(3, 3);

				return result;
			}

			inline Vector3 operator *(const Matrix& rMatrix, const Vector3& rVector)
			{
				return rVector * rMatrix;
			}
		}
	}
}

#endif